
#include <stdio.h>

#include "land_graphics.h"

#define LAND_OGL_CORE

#include "init.h"

static struct {
	LandCanvas* curBind;
}ctx = { 0 };


static size_t
sizeofCanvas(){
	return sizeof(LandOglCanvas);
}

static GLsizei
getMsaaCount(LandMsaa msaa) {
	switch (msaa) {
	case LAND_MSAA_0:
		return 0;
	case LAND_MSAA_4:
		return 4;
	case LAND_MSAA_8:
		return 8;
	case LAND_MSAA_16:
		return 16;
	}
	return 0;
}

static int
newCanvas(LandCanvas* canvas) {
	GLsizei width = (GLsizei)canvas->width;
	GLsizei height = (GLsizei)canvas->height;
	GLsizei msaa = getMsaaCount(canvas->msaa);

	GLuint fboRead;
	glGenFramebuffers(1, &fboRead);
	glBindFramebuffer(GL_FRAMEBUFFER, fboRead);

	GLuint texRead;
	glGenTextures(1, &texRead);
	glBindTexture(GL_TEXTURE_2D, texRead);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
		width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glFramebufferTexture2D(GL_FRAMEBUFFER,
		GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texRead, 0);

	GLuint fboDraw = fboRead;
	if (msaa > 0) {
		glGenFramebuffers(1, &fboDraw);
		glBindFramebuffer(GL_FRAMEBUFFER, fboDraw);
	}
	GLuint texDepth;
	glGenTextures(1, &texDepth);
	glBindTexture(GL_TEXTURE_2D, texDepth);
	if (msaa > 0) {
		glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE,
			msaa, GL_DEPTH24_STENCIL8, width, height, GL_TRUE);
		glFramebufferTexture2D(GL_FRAMEBUFFER,
			GL_DEPTH_STENCIL_ATTACHMENT,
			GL_TEXTURE_2D, texDepth, 0);
	}
	else {
		glTexImage2D(GL_TEXTURE_2D,
			0, GL_DEPTH24_STENCIL8, width, height, 0,
			GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL);
		glFramebufferTexture2D(GL_FRAMEBUFFER,
			GL_DEPTH_STENCIL_ATTACHMENT,
			GL_TEXTURE_2D, texDepth, 0);
	}
	GLuint texDraw = texRead;
	if (msaa > 0) {
		glGenTextures(1, &texDraw);
		glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, texDraw);
		glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE,
			msaa, GL_RGBA, width, height, GL_TRUE);
		glFramebufferTexture2D(GL_FRAMEBUFFER,
			GL_COLOR_ATTACHMENT0,
			GL_TEXTURE_2D_MULTISAMPLE, texDraw, 0);

	}

	GLuint rbo;
	glGenRenderbuffers(1, &rbo);
	glBindRenderbuffer(GL_RENDERBUFFER, rbo);
	if (msaa > 0) {
		glRenderbufferStorageMultisample(GL_RENDERBUFFER,
			msaa, GL_DEPTH24_STENCIL8, width, height);
	}
	else {
		glRenderbufferStorage(GL_RENDERBUFFER,
			GL_DEPTH24_STENCIL8, width, height);
	}
	glFramebufferRenderbuffer(GL_FRAMEBUFFER,
		GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

	GLint err = glGetError();
	if (err) {
		printf("newCanvas err:%d\n", err);
	}

	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
		printf("Framebuffer is not complete!\n");
	}

	LandOglCanvas* oc = (LandOglCanvas*)canvas;
	oc->fboRead = fboRead;
	oc->fboDraw = fboDraw;
	oc->texRead = texRead;
	oc->texDraw = texDraw;
	oc->texDepth = texDepth;
	oc->rbo = rbo;
	return 0;
}


static void
freeCanvas(LandCanvas* canvas) {
	LandOglCanvas* oc = (LandOglCanvas*)canvas;
	if (ctx.curBind == canvas) {
		landBindOglCanvas(NULL);
	}
	glDeleteTextures(1, &oc->texRead);
	glDeleteTextures(1, &oc->texDepth);
	glDeleteRenderbuffers(1, &oc->rbo);
	glDeleteFramebuffers(1, &oc->fboRead);
	if (canvas->msaa != LAND_MSAA_0) {
		glDeleteTextures(1, &oc->texDraw);
		glDeleteFramebuffers(1, &oc->fboDraw);
	}
}

static void
clearCanvas(LandCanvas* canvas) {
	LandOglCanvas* oc = (LandOglCanvas*)canvas;
	GLsizei width = (GLsizei)canvas->width;
	GLsizei height = (GLsizei)canvas->height;
	landBindOglCanvas(canvas);

	LandVector color = canvas->color;
	glDepthMask(GL_TRUE);
	glClearColor((GLfloat)color.x, (GLfloat)color.y,
		(GLfloat)color.z, (GLfloat)color.w);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
	GLint err = glGetError();
	if (err) {
		printf("clearCanvas err:%d\n", err);
	}
}

static void
resizeCanvas(LandCanvas* canvas) {
	LandOglCanvas* oc = (LandOglCanvas*)canvas;
	GLsizei width = (GLsizei)canvas->width;
	GLsizei height = (GLsizei)canvas->height;
	GLsizei msaa = getMsaaCount(canvas->msaa);
	landBindOglCanvas(canvas);
	glBindTexture(GL_TEXTURE_2D, oc->texRead);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
		width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	glBindTexture(GL_TEXTURE_2D, oc->texDepth);
	if (msaa > 0) {
		glBindTexture(GL_TEXTURE_2D, oc->texDepth);
		glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE,
			msaa, GL_DEPTH24_STENCIL8, width, height, GL_TRUE);

		glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, oc->texDraw);
		glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE,
			msaa, GL_RGBA, width, height, GL_TRUE);

		glBindRenderbuffer(GL_RENDERBUFFER, oc->rbo);
		glRenderbufferStorageMultisample(GL_RENDERBUFFER,
			msaa, GL_DEPTH24_STENCIL8, width, height);
	}
	else {
		glBindTexture(GL_TEXTURE_2D, oc->texDepth);
		glTexImage2D(GL_TEXTURE_2D,
			0, GL_DEPTH24_STENCIL8, width, height, 0,
			GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL);

		glBindRenderbuffer(GL_RENDERBUFFER, oc->rbo);
		glRenderbufferStorage(GL_RENDERBUFFER,
			GL_DEPTH24_STENCIL8, width, height);
	}
	GLint err = glGetError();
	if (err) {
		printf("resizeCanvas err:%d\n", err);
	}
}

static void
flushCanvas(LandCanvas* canvas) {
	LandOglCanvas* oc = (LandOglCanvas*)canvas;
	if (canvas->msaa == LAND_MSAA_0) {
		return;
	}
	GLsizei width = (GLsizei)canvas->width;
	GLsizei height = (GLsizei)canvas->height;
	glBindFramebuffer(GL_READ_FRAMEBUFFER, oc->fboDraw);
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oc->fboRead);
	glBlitFramebuffer(0, 0, width, height,
		0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
	GLint err = glGetError();
	if (err) {
		printf("loglReadyCanvasColor err:%d\n", err);
	}
}


static LandCanvasBackend backend = {
	sizeofCanvas,
	newCanvas,
	freeCanvas,
	clearCanvas,
	resizeCanvas,
	flushCanvas,
};

LandCanvasBackend*
landGetOglCanvasBackend() {
	return &backend;
}

void
landBindOglCanvas(LandCanvas* canvas) {
	ctx.curBind = canvas;
	if (canvas) {
		LandOglCanvas* oglCanvas = (LandOglCanvas*)canvas;
		glBindFramebuffer(GL_FRAMEBUFFER, oglCanvas->fboDraw);
		glViewport(0, 0, canvas->width, canvas->height);
	}
	else {
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
	}
}
GLuint
landGetOglCanvasTexId(LandCanvas* canvas) {
	LandOglCanvas* oglCanvas = (LandOglCanvas*)canvas;
	return ((LandOglCanvas*)canvas)->texRead;
}